package controller;

import controller.command.*;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import model.displayer.*;
import model.Point;
import model.Shape;
import java.util.*;

public class MainController
{
    private Shape selectedShapeType = null;
    private ShapeDisplayer currentSelectedShape = null;
    private Deque<ShapeDisplayer> selectedShapes = new LinkedList<>();
    private Point mousePressed,mouseDown,mouseReleased;
    private ShapeDisplayer currentDrawingShape = null;
    private ShapeDisplayer clipBoard = null;
    private String currentContent;

    private Deque<Command> undoStack = new LinkedList<>();

    private void executeCommand(Command cmd,boolean isExecuted)
    {
        if(isExecuted)
        {
            ShapeDisplayer shape = ((CommandJFxImpl)cmd).getShape();
            if(cmd instanceof ShapeClearCmd || cmd instanceof ShapeResolveCmd)
            {
                if(selectedShapes.contains(shape))
                    controlSwitchOff(shape);
            }
            else if(cmd instanceof ShapeCombineCmd)
            {
                while(currentSelectedShape != null)
                    controlSwitchOff(currentSelectedShape);
            }
            cmd.execute();
        }
        undoStack.addLast(cmd);
    }

    private void executeCommand(Command cmd) {
        executeCommand(cmd,true);
    }

    private void cancelCommand(Command cmd)
    {
        ShapeDisplayer shape = ((CommandJFxImpl)cmd).getShape();
        if(cmd instanceof ShapeShowCmd || cmd instanceof ShapeCombineCmd)
        {
            if(selectedShapes.contains(shape))
                controlSwitchOff(shape);
            cmd.cancel();
        }
        else if(cmd instanceof ShapeResolveCmd)
        {
            cmd.cancel();
            for(ShapeDisplayer child : shape)
                if(selectedShapes.contains(child))
                    controlSwitchOff(child);
        }
        else cmd.cancel();
    }

    private void controlSwitchOff(ShapeDisplayer shape)
    {
        selectedShapes.remove(shape);
        shape.setSelected(false);

        if(currentSelectedShape == shape)
        {
            shape.setOutStanding(false);
            currentSelectedShape = selectedShapes.peekLast();

            if(currentSelectedShape != null)
                currentSelectedShape.setOutStanding(true);
        }
    }
    
    private void controlSwitchOn(ShapeDisplayer shape)
    {
        selectedShapes.addLast(shape);
        shape.setSelected(true);
        if(currentSelectedShape != null)
            currentSelectedShape.setOutStanding(false);

        currentSelectedShape = shape;
        currentSelectedShape.setOutStanding(true);
    }

    public String getHelpInformation()
    {
        return "This project is a programming project of the \n" +
                "OOP-Spring2021 course of Nanjing University.\n" +
                "It is written by Ke Cheng, an undergraduate \n" +
                "in School of Information Management.\n"+
                "If you have any questions, you can mail to kecheng.nju@gmail.com for help.";
    }

    public void handleTextAreaChanged(String newStr) { currentContent = newStr.trim(); }

    public void handleTextSubmit(Pane pane)
    {
        if(currentSelectedShape instanceof TextDisplayer) //modify old Text
        {
            TextDisplayer text = (TextDisplayer)currentSelectedShape;
            Command textCmd = CommandFactory.getInstance().getTextChangeCmd(text,currentContent);
            executeCommand(textCmd);
        }
        else if(currentContent != null && currentContent.length() != 0)//Create new Text
        {
            TextDisplayer text = (TextDisplayer) ShapeDisplayerJFxFactory.getInstance().
                    getShapeDisplayer(Shape.Text,pane,this);
            text.setContent(currentContent);
            Command showCmd = CommandFactory.getInstance().getShowCmd(text);
            executeCommand(showCmd);
        }
    }

    public void handleUndo()
    {
        System.out.println("handleUndo");
        Command cmd = undoStack.pollLast();
        if(cmd == null)return ;

        cancelCommand(cmd);
    }

    public void handleComposite()
    {
        if(selectedShapes.size() < 2)return ;
        ShapeCombineCmd combineCmd = CommandFactory.getInstance().getCombineCmd(this.selectedShapes.iterator());
        executeCommand(combineCmd);
    }

    public void handleResolve()
    {
        if(currentSelectedShape == null || currentSelectedShape.iterator() == null) return ;
        Command resolveCmd = CommandFactory.getInstance().getResolveCmd(currentSelectedShape);
        executeCommand(resolveCmd);
    }

    public void handleCopy()
    {
        System.out.println("handleCopy!");
        if(currentSelectedShape == null)return;
        this.clipBoard = currentSelectedShape;
    }

    public void handlePaste()
    {
        System.out.println("handlePaste!");
        if(clipBoard == null) return;

        int copyMoveX = 20, copyMoveY = 20;
        ShapeDisplayer copy = clipBoard.clone();
        copy.move(copyMoveX,copyMoveY);

        Command showCmd = CommandFactory.getInstance().getShowCmd(copy);
        executeCommand(showCmd);
    }

    public void handleDelete()
    {
        System.out.println("handleDelete");
        if(currentSelectedShape!=null)
        {
            Command delCmd = CommandFactory.getInstance().getClearCmd(currentSelectedShape);
            executeCommand(delCmd);
        }
    }

    private ShapeDisplayer getAncestor(ShapeDisplayer shape)
    {
        if(shape.getFather() == null)
            return shape;
        else
            return getAncestor(shape.getFather());
    }

    public void handleShapeClicked(ShapeDisplayer shape)
    {
        ShapeDisplayer ancestor = getAncestor(shape);

        if(!selectedShapes.contains(ancestor))
            controlSwitchOn(ancestor);
        else
            controlSwitchOff(ancestor);
    }

    public void handleShapeBtnClicked(Shape type)
    {
        System.out.println("handleShapeBtnClicked");
        this.selectedShapeType = type;
    }

    public void handleColorPicked(Color color)
    {
        if(currentSelectedShape!=null)
        {
            Command colorCmd = CommandFactory.getInstance().getColorChangeCmd(currentSelectedShape,color);
            executeCommand(colorCmd);
        }
    }

    public void handleExpandBtnClicked()
    {
        if(currentSelectedShape != null)
        {
            Command expandCmd = CommandFactory.getInstance().getExpandCmd(currentSelectedShape,0.1);
            executeCommand(expandCmd);
        }
    }

    public void handleShrinkBtnClicked()
    {
        if(currentSelectedShape != null)
        {
            Command shrinkCmd = CommandFactory.getInstance().getShrinkCmd(currentSelectedShape,0.1);
            executeCommand(shrinkCmd);
        }
    }

    public void handleNewShapeStarted(Point start)
    {
        this.mousePressed = start.clone();
        this.mouseDown = start.clone();
        if(selectedShapeType != null)
        {
            System.out.println("handleNewShapeStarted");
        }
    }

    public void handleMouseDragged(Point process,Pane pane)
    {
        if(selectedShapeType != null)
        {
            if(currentDrawingShape == null)
                currentDrawingShape = ShapeDisplayerJFxFactory.getInstance().
                                      getShapeDisplayer(selectedShapeType,pane,this);
            currentDrawingShape.setLocation(mousePressed,process);
            currentDrawingShape.show();
        }
        else if(currentSelectedShape != null)
        {
            double x = process.getX() - mouseDown.getX();
            double y = process.getY() - mouseDown.getY();
            currentSelectedShape.move(x,y);
        }
        mouseDown = process.clone();
    }


    public void handleNewShapeFinished(Point end)
    {
        mouseReleased = end;

        if(selectedShapeType != null)
        {
            if(mouseReleased.getX() != mousePressed.getX() || mouseReleased.getY() != mousePressed.getY())
            {
                Command showCmd = CommandFactory.getInstance().getShowCmd(currentDrawingShape);
                executeCommand(showCmd);
            }
            currentDrawingShape = null;
            selectedShapeType = null;
            mousePressed = null;
            mouseDown = null;
            System.out.println("handleNewShapeEnd");
        }
        else if(currentSelectedShape != null)
        {
            if(mouseReleased.getX() != mousePressed.getX() || mouseReleased.getY() != mousePressed.getY())
            {
                double x = mouseReleased.getX() - mousePressed.getX();
                double y = mouseReleased.getY() - mousePressed.getY();
                Command moveCmd = CommandFactory.getInstance().getMoveCmd(currentSelectedShape,x,y);
                executeCommand(moveCmd,false);
            }
        }
    }
}